/*
 * Copyright 2012-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.context.event;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ApplicationEventMulticaster;
import org.springframework.context.event.SimpleApplicationEventMulticaster;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.util.ErrorHandler;

/**
 * {@link SpringApplicationRunListener} to publish {@link SpringApplicationEvent}s.
 * <p>
 * Uses an internal {@link ApplicationEventMulticaster} for the events that are fired
 * before the context is actually refreshed.
 *
 * @author Phillip Webb
 * @author Stephane Nicoll
 * @author Andy Wilkinson
 * @author Artsiom Yudovin
 * @since 1.0.0
 */
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {

	private final SpringApplication application;

	private final String[] args;

	private final SimpleApplicationEventMulticaster initialMulticaster;

	//这里初始化是容器启动时的一个事件发布者，这里创建了事件发布者SimpleApplicationEventMulticaster
	public EventPublishingRunListener(SpringApplication application, String[] args) {
		this.application = application;
		this.args = args;
		this.initialMulticaster = new SimpleApplicationEventMulticaster();
		/**
		 * application.getListeners()得到的是在调用run方法的时候，在SpringApplication构造
		 * 方法中从spring.factories中读取的key=ApplicationListener的所有监听器列表
		 * 这里获取到所有的监听器，然后通过时间发布者对象添加到监听器列表中
		 * 这里将spring.factaories中类型是ApplicationListener的监听器都取出来然后加载
		 */
		for (ApplicationListener<?> listener : application.getListeners()) {
			this.initialMulticaster.addApplicationListener(listener);
		}
	}

	@Override
	public int getOrder() {
		return 0;
	}

	@Override
	public void starting() {
		//这里发布一个事件为ApplicationStartingEvent的事件，监听器中如果发现有和这个感兴趣的监听者会是触发
		//这个事件代表的意思就是说容器启动了，如果你有想要做的事情，就去做，怎么做？编写一个监听器，监听器的类型是ApplicationStartingEvent
		//然后重写就可以了,这里发布的这个事件的意思就是容器在启动之前发布要做的事情，如果说你需要在容器启动的时候
		//做一些事情，那么就可以定义一个自己的监听器，监听器的类型是ApplicationStartingEvent就可以监听到了
		//但是使用有一定的规范的，因为这个时候容器没有启动，这个是spring boot的监听器，所以需要在spring.factories中定义才会被监听执行
		//这里发布的事件是由spring去匹配通知符合的监听器去执行的
		this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
	}

	/**
	 * 这里是发布一个事件，这个事件的类型是ApplicationEnvironmentPreparedEvent
	 * 是环境资源信息准备好的一个事件，这个事件由ConfigFileApplicationListener
	 * 这监听器来接受并且执行的，执行的结果就是解析配置文件application.yml文件
	 * 然后将解析得到的内容放入环境变量中,所以如果要深入看是如何解析这个配置文件的
	 * 直接进入ConfigFileApplicationListener中的onApplicationEvent方法即可
	 * @param environment the environment
	 */
	@Override
	public void environmentPrepared(ConfigurableEnvironment environment) {
		this.initialMulticaster
				.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
	}

	@Override
	public void contextPrepared(ConfigurableApplicationContext context) {
		this.initialMulticaster
				.multicastEvent(new ApplicationContextInitializedEvent(this.application, this.args, context));
	}

	/**
	 * 这里发布的事件是容器上下文已经加载完毕，马上就要开始刷新容器了
	 * 所以这里发布一个事件， 因为
	 * 你想，容器上下文已经准备好，并且配置类已经laod，这个时候马上要开始
	 * 刷新容器了，而这个时候，spring boot的监听器还没有在容器中，所以
	 * 这里将spring boot的监听器通过contxt添加到ioc中成为一个bean
	 * spring的监听器主要针对的是IOC中的bean，所以这里的思路就是将spring  boot
	 * 的监听器交给spring的IOC来管理
	 * 将spring boot的监听器交给spring ioc过后，这里再发布一个事件
	 * 表示容器已经load完毕了，要开始刷新容器了
	 * @param context the application context
	 */
	@Override
	public void contextLoaded(ConfigurableApplicationContext context) {
		for (ApplicationListener<?> listener : this.application.getListeners()) {
			if (listener instanceof ApplicationContextAware) {
				((ApplicationContextAware) listener).setApplicationContext(context);
			}
			context.addApplicationListener(listener);
		}
		this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application, this.args, context));
	}

	@Override
	public void started(ConfigurableApplicationContext context) {
		context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
	}

	@Override
	public void running(ConfigurableApplicationContext context) {
		context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context));
	}

	@Override
	public void failed(ConfigurableApplicationContext context, Throwable exception) {
		ApplicationFailedEvent event = new ApplicationFailedEvent(this.application, this.args, context, exception);
		if (context != null && context.isActive()) {
			// Listeners have been registered to the application context so we should
			// use it at this point if we can
			context.publishEvent(event);
		}
		else {
			// An inactive context may not have a multicaster so we use our multicaster to
			// call all of the context's listeners instead
			if (context instanceof AbstractApplicationContext) {
				for (ApplicationListener<?> listener : ((AbstractApplicationContext) context)
						.getApplicationListeners()) {
					this.initialMulticaster.addApplicationListener(listener);
				}
			}
			this.initialMulticaster.setErrorHandler(new LoggingErrorHandler());
			this.initialMulticaster.multicastEvent(event);
		}
	}

	private static class LoggingErrorHandler implements ErrorHandler {

		private static final Log logger = LogFactory.getLog(EventPublishingRunListener.class);

		@Override
		public void handleError(Throwable throwable) {
			logger.warn("Error calling ApplicationEventListener", throwable);
		}

	}

}
